// file:system/account/account.c
// autor:jiangxinpeng
// time:2021.6.8
// copyright:(C) by jiangxinpeng,All right are reserved.

#include <os/account.h>
#include <os/mutexlock.h>
#include <os/memcache.h>
#include <os/debug.h>
#include <os/sconfig.h>
#include <os/safety.h>
#include <os/kernelif.h>
#include <lib/type.h>
#include <lib/errno.h>
#include <lib/string.h>
#include <lib/unistd.h>
#include <lib/math.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>

account_t *account_table = NULL;
account_t *cur_user = NULL;

mutexlock_t account_mutex_lock;

// gloabl permiss database
permiss_database_t *permiss_db = NULL;

// initilization account
void AccountInit(account_t *account)
{
    account->uid = 0;
    account->gid = 0;
    memset(account->name, 0, ACCOUNT_NAME_LEN);
    memset(account->password, 0, ACCOUNT_PASSWORD_LEN);
    memset(account->data_index, -1, PERMISS_DATABASE_LEN);
    account->idx_len = 0;
    account->flags = 0;
    SpinLockInit(&account->lock);
}

// add database index to account
int AccountAddIndex(account_t *accout, uint32_t index)
{
    SpinLockDisInterrupt(&accout->lock);
    for (int i = 0; i < PERMISS_DATABASE_LEN; i++)
    {
        if (accout->data_index[i] < 0)
        {
            accout->data_index[i] = index;
            accout->idx_len++;
            SpinUnlockEnInterrupt(&accout->lock);
            return 0;
        }
    }
    SpinUnlockEnInterrupt(&accout->lock);
    return -1;
}

// delete database index from account
int AccoutDelIndex(account_t *accout, uint32_t index)
{
    SpinLockDisInterrupt(&accout->lock);
    for (int i = 0; i < PERMISS_DATABASE_LEN; i++)
    {
        if (accout->data_index[i] == index)
        {
            accout->data_index[i] = -1;
            accout->idx_len--;
            SpinUnlockEnInterrupt(&accout->lock);
            return 0;
        }
    }
    SpinUnlockEnInterrupt(&accout->lock);
    return 0;
}

// alloc free account
account_t *AccountAlloc()
{
    account_t *account;

    MutexlockLock(&account_mutex_lock, MUTEX_LOCK_MODE_BLOCK);
    for (int i = 0; i < ACCOUNT_NUM_MAX; i++)
    {
        account = account_table + i;
        if (!(account->flags & ACCOUNT_FLAG_USING))
        {
            account->uid = i;
            account->flags |= ACCOUNT_FLAG_USING;
            MutexlockUnlock(&account_mutex_lock);
            return account;
        }
    }
    MutexlockUnlock(&account_mutex_lock);
    return NULL;
}

// free account
int AccountFree(account_t *account)
{
    MutexlockLock(&account_mutex_lock, MUTEX_LOCK_MODE_BLOCK);
    if (account != NULL)
    {
        account->flags &= (~ACCOUNT_FLAG_USING);
        account->uid = -1;
        MutexlockUnlock(&account_mutex_lock);
        return 0;
    }
    MutexlockUnlock(&account_mutex_lock);
    return -1;
}

int AccountManagerInit()
{
    MutexlockInit(&account_mutex_lock);
    // alloc account table mem
    account_table = KMemAlloc(sizeof(account_t) * ACCOUNT_NUM_MAX);
    if (account_table != NULL)
    {
        for (int i = 0; i < ACCOUNT_NUM_MAX; i++)
        {
            AccountInit(account_table + i);
        }
        // current login user
        cur_user = NULL;
        PermissionDataBaseInit();
        if (AccountReadConfig() < 0)
            Panic("account config read failed!\n");
        if (AccountLogin(ROOT_ACCOUNT_NAME, ROOT_ACCOUNT_PASSWORD) < 0)
            Panic("account: login root account failed!\n");
        KPrint("[account] account manger init ok\n");
        return 0;
    }
    return -1;
}

int AccountRegister(const char *name, char *password, uint32_t flags)
{
    account_t *account;
    char home[32];
    int index;

    if (name != NULL && password != NULL)
    {
        if (AccountNameCheck(name) != 0)
            return -EINVAL;
        if (AccountPasswordCheck(password) != 0)
            return -EINVAL;

        // current user present
        if (cur_user != NULL)
        {
            // cur user permission no enough to finish opertor
            if ((cur_user->flags & ACCOUNT_LEVEL_MASK) != ACCOUNT_LEVEL_ROOT)
            {
                return -EPERM;
            }
        }

        // check targe account whether had present
        account = AccountFindByName(name);
        if (account != NULL)
        {
            return -1;
        }
        // create account
        if (!AccountPush(name, password, flags))
        {
            // create home path
            // path style follow: /home/username
            strcpy(home, HOME_DIR_PATH);
            strcat(home, "/");
            strcat(home, name);
            // set permission database
            index = PermissionDataBaseInsert(PERMISS_ATTR_HOME | PERMISS_ATTR_RDWR, home);
            if (index < 0)
            {
                AccountPop(name);
                return -1;
            }
            // get new account and insert permission database index
            account = AccountFindByName(name);
            AccountAddIndex(account, index);
            // sync data
            if (AccountSync() < 0)
            {
                PermissionDataBaseDel(index);
                AccountPop(name);
                KPrint("[account] sync data failed!\n");
                return -1;
            }
            return 0;
        }
    }
    return -1;
}

int AccountUnRegister(const char *name)
{
    account_t *account;
    account_t account_backup; // backup account data
    permiss_data_t data_backup;
    char home[32];

    if (name != NULL)
    {
        // check currrent user
        if (cur_user != NULL)
        {
            if (cur_user->flags & ACCOUNT_LEVEL_MASK != ACCOUNT_LEVEL_ROOT)
            {
                return -1;
            }
        }
        account = AccountFindByName(name);
        if (account != NULL)
        {
            // no can unregister currrent user
            if (cur_user != NULL && account == cur_user)
            {
                return -1;
            }
            // backup account
            account_backup = *account;
            // make home path
            strcpy(home, HOME_DIR_PATH);
            strcat(home, "/");
            strcat(home, name);
            // backup permission data
            data_backup = *PermissionDataBaseSelect(home);
            // del system register info
            if (PermissionDataBaseDelByData(home) < 0)
                return -1;
            if (AccountPop(account->name) < 0)
            {
                // if account present but pop faild,refresh permission data
                PermissionDataBaseInsert(data_backup.attr, data_backup.str);
                return -1;
            }
            // if sync faild,register account again
            if (AccountSync() < 0)
            {
                AccountPush(account_backup.name, account_backup.password, account_backup.flags);
                PermissionDataBaseInsert(data_backup.attr, data_backup.str);
                return -1;
            }
            return 0;
        }
    }
    return -1;
}

int AccountNameCheck(const char *name)
{
    char *p = (char *)name;

    if (p != NULL)
    {
        while (*p != '\0')
        {
            if (!ACCOUNT_NAME_VALID(*p))
                return -1;
            p++;
        }
    }
    if (strlen(p) >= ACCOUNT_NAME_MIN_LEN)
    {
        return -1;
    }
    return 0;
}

int AccountPasswordCheck(const char *name)
{
    char *p = (char *)name;

    if (p != NULL)
    {
        while (*p != '\0')
        {
            if (!ACCOUNT_PASSWORD_VALID(*p))
                return -1;
            p++;
        }
    }
    if (strlen(p) >= ACCOUNT_PASSWORD_MIN_LEN)
    {
        return -1;
    }
    return 0;
}

account_t *AccountFindByName(const char *name)
{
    account_t *account;

    for (int i = 0; i < ACCOUNT_NUM_MAX; i++)
    {
        account = account_table + i;
        if ((account->flags & ACCOUNT_FLAG_USING) && (!strcmp(account->name, name)))
        {
            return account;
        }
    }
    return NULL;
}

// push a account to system account table
int AccountPush(const char *name, char *password, uint32_t attr)
{
    account_t *account = AccountAlloc();
    if (account != NULL)
    {
        strcpy(account->name, name);
        strcpy(account->password, password);
        account->flags |= attr;
        return 0;
    }
    return -1;
}

// pop a account from system account table
int AccountPop(const char *name)
{
    account_t *account = AccountFindByName(name);
    if (account != NULL)
    {
        AccountFree(account);
        return 0;
    }
    return -1;
}

int AccountSelfPermissionCheck(const char *str, uint32_t attr)
{
    if (cur_user != NULL)
    {
        return AccountPermissionCheck(cur_user, (char *)str, attr);
    }
    return -1;
}

int AccountPermissionCheck(account_t *account, char *str, uint32_t attr)
{
    /*permiss_data_t *data = NULL;

    for (int i = 0; i < PERMISS_DATABASE_LEN; i++)
    {
        if (account->data_index[i] != -1)
        {
            data = PermissionDataBaseSelectByIndex(i);
            if (data->attr == attr && !strcmp(data->str, str))
                return 0;
        }
    }
    return -1;*/
    return 0;
}

int AccountReadConfig()
{
    char buff[32];
    int fd;
    int file_exist = 1;

    strcpy(buff, ACCOUNT_DIR_PATH);
    strcat(buff, "/");
    strcat(buff, ACCOUNT_FILE_NAME);
    if (KFileAccess(buff, F_OK) < 0)
    {
        fd = KFileOpen(buff, O_CREATE | O_RDWR);
        if (fd < 0)
        {
            KPrint("[account] %s:account file %s create failed!\n", __func__, buff);
            return -1;
        }
        KFileClose(fd);
        file_exist = 0;
    }
    if (!file_exist) // no exist
    {
        if (AccountPush(ROOT_ACCOUNT_NAME, ROOT_ACCOUNT_NAME, ACCOUNT_LEVEL_ROOT) < 0)
        {
            KPrint(PRINT_ERR "create account failed!\n");
            return -1;
        }
        if (AccountSync() < 0)
        {
            KPrint(PRINT_ERR "sync account to file failed!\n");
            return -1;
        }
    }
    else
    {
        if (AccountLoad(buff) < 0)
        {
            KPrint(PRINT_ERR "account load from file %s failed!\n", buff);
            return -1;
        }
    }
    return 0;
}

static int AccountLoad(char *file)
{
    int fd;
    int size;
    char *buff;
    char *p;
    char line[ACCOUNT_CONFIG_BUFF_SIZE];

    if (!file)
        return -1;
    fd = KFileOpen(file, O_RDONLY);
    if (fd < 0)
        return -1;
    status_t stu;
    KFileStatus(file, &stu);
    size = stu.st_size;
    if (size <= 0)
        return -1;
    buff = KMemAlloc(size);
    if (!buff)
    {
        KFileClose(fd);
        return -1;
    }
    if (KFileRead(fd, buff, size) != size)
    {
        KMemFree(buff);
        KFileClose(fd);
        return -1;
    }
    KFileClose(fd);

    p = buff;
    while (1)
    {
        p = SConfigReadLine(p, line, 128);
        if (!p)
            break;
        if (AccountScanLine(line) < 0)
            return -1;
    }
    KMemFree(buff);
    return 0;
}

int AccountScanLine(char *line)
{
    char *q = line;
    uint32_t attr = 0;
    char attr_buff[ACCOUNT_ATTR_BUFF_MAX];
    char name_buff[ACCOUNT_NAME_BUFF_MAX];
    char pass_buff[ACCOUNT_PASSWORD_BUFF_MAX];

    q = SConfigRead(q, attr_buff, ACCOUNT_ATTR_BUFF_MAX);
    if (!q)
    {
        KPrint(PRINT_ERR "account: get attr failed!\n");
        return -1;
    }
    SConfigTrim(attr_buff);
    if (attr_buff[0] == 'S')
        attr |= ACCOUNT_LEVEL_ROOT;
    else if (attr_buff[0] == 'U')
        attr |= ACCOUNT_LEVEL_USER;
    q = SConfigRead(q, name_buff, ACCOUNT_NAME_BUFF_MAX);
    if (!q)
    {
        KPrint("account: get name failed!\n");
        return -1;
    }
    SConfigTrim(name_buff);
    q = SConfigRead(q, pass_buff, ACCOUNT_PASSWORD_BUFF_MAX);
    if (!q)
    {
        KPrint("account: get password failed!\n");
        return -1;
    }
    SConfigTrim(pass_buff);
    return AccountPush(name_buff, pass_buff, attr);
}

int AccountSync()
{
    if (AccountSyncData() < 0)
        return -1;
    if (PermissionDataBaseSync() < 0)
        return -1;
    return 0;
}

int AccountSyncData()
{
    account_t *account;
    char buff[32];
    int fd;
    char account_buff[ACCOUNT_CONFIG_BUFF_SIZE];

    MutexlockLock(&account_mutex_lock, MUTEX_LOCK_MODE_BLOCK);
    // build account file path
    strcpy(buff, ACCOUNT_DIR_PATH);
    strcat(buff, "/");
    strcat(buff, ACCOUNT_FILE_NAME);
    fd = KFileOpen(buff, O_RDWR);
    if (fd < 0)
    {
        MutexlockUnlock(&account_mutex_lock);
        return -1;
    }
    for (int i = 0; i < ACCOUNT_NUM_MAX; i++)
    {
        account = account_table + i;
        if (account->flags & ACCOUNT_FLAG_USING)
        {
            memset(account_buff, 0, ACCOUNT_CONFIG_BUFF_SIZE);
            AccountBuildFileBuff(account, account_buff);
            //KPrint("[account] write %s", account_buff);
            KFileWrite(fd, account_buff, strlen(account_buff));
        }
    }
    KFileWrite(fd, "\0", 1);
    KFileClose(fd);
    MutexlockUnlock(&account_mutex_lock);
    return 0;
}

static void AccountBuildFileBuff(account_t *account, char *buff)
{
    int level = account->flags & ACCOUNT_LEVEL_MASK;
    char *level_str = (level != ACCOUNT_LEVEL_ROOT) ? "U" : "S";
    SConfigWrite(buff, level_str);
    SConfigWrite(buff, account->name);
    SConfigWrite(buff, account->password);
    SConfigWriteLine(buff);
}

static void _AccountBindPermission(void *arg, void *self)
{
    account_t *account = arg;
    permiss_data_t *data = self;
    char *path, *slash;

    if (data->attr & PERMISS_ATTR_HOME)
    {
        path = data->str;
        slash = (char *)strrchr(path, '/');
        if (slash)
        {
            slash++;
            if (!strcmp(slash, account->name))
                return;
        }
    }
    AccountAddIndex(account, data - permiss_db->data);
}

static int AccountBindPermission(account_t *account)
{
    if ((account->flags & ACCOUNT_LEVEL_MASK) < ACCOUNT_LEVEL_USER)
        account->idx_len = 0;
    else
        PermissionDataBaseForEach(_AccountBindPermission, account);
}

static int AccountDeBindPermission(account_t *account)
{
    for (int i = 0; i < PERMISS_DATABASE_LEN; i++)
    {
        account->data_index[i] = -1;
    }
    return 0;
}

int AccountLogin(const char *name, const char *password)
{
    account_t *account;
    if (!name || !password)
        return -1;
    KPrint("[account] login name %s pwd %s\n",name,password);
    if (AccountNameCheck(name) < 0 || AccountPasswordCheck(password) < 0)
    {
        KPrint("account info invalid\n");
        return -1;
    }
    account = AccountFindByName(name);
    if (!account)
    {
        KPrint("account %s no exist!\n", name);
        return -1;
    }
    SpinLock(&account->lock);
    if (strcmp(password, account->password))
    {
        KPrint(PRINT_ERR "account login: %s login failed!\n", name);
        return -1;
    }
    if ((account->flags & ACCOUNT_STU_LOGINED))
    {
        // force debind all permission
        AccountDeBindPermission(account);
        account->flags &= ~ACCOUNT_STU_LOGINED;
    }
    AccountBindPermission(account);
    SpinUnlock(&account->lock);
    MutexlockLock(&account_mutex_lock, MUTEX_LOCK_MODE_BLOCK);
    cur_user = account;
    cur_user->flags |= ACCOUNT_STU_LOGINED;
    KPrint("account login: %s login system ok! cur user %s\n", name, cur_user->name);
    MutexlockUnlock(&account_mutex_lock);
    return 0;
}

int AccountLogout(const char *name)
{
    account_t *account;

    if (!name)
        return -1;
    if (AccountNameCheck(name) < 0)
        return -1;
    // find account object
    account = AccountFindByName(name);
    if (!account)
    {
        KPrint("[acount] account %s no exist!\n", name);
        return -1;
    }
    if (!cur_user)
    {
        KPrint("[account] sorry,system no any active account!\n");
        return -1;
    }
    if (account != cur_user)
    {
        KPrint("[account] account %s no login!\n", name);
        return -1;
    }
    // clear account login flags
    SpinLock(&account->lock);
    if (!account->flags & ACCOUNT_STU_LOGINED)
    {
        KPrint("[acount] account  %s don not need exit!\n", name);
        return -1;
    }
    SpinUnlock(&account->lock);
    if (AccountDeBindPermission(account) < 0)
    {
        KPrint("[account] account %s exit failed!\n", name);
        return -1;
    }
    MutexlockLock(&account_mutex_lock, MUTEX_LOCK_MODE_BLOCK);
    cur_user->flags &= ~ACCOUNT_STU_LOGINED;
    cur_user = NULL;
    KPrint("[account] user %s logout!\n", account->name);
    MutexlockUnlock(&account_mutex_lock);
    return 0;
}

// change account password
int AccountChPwd(char *name, char *old_password, char *new_password)
{
    if (!name || !old_password || !new_password)
        return -1;
    if (AccountNameCheck(name) < 0 || AccountPasswordCheck(old_password) < 0 || AccountPasswordCheck(new_password) < 0)
        return -1;
    account_t *account = AccountFindByName(name);
    if (!account)
    {
        KPrint("[account] targe user %s no find\n", name);
        MutexlockUnlock(&account_mutex_lock);
        return -1;
    }
    SpinLock(&account->lock);
    KPrint("[account] change user %s old pwd %s new pwd %s\n", name, old_password, new_password);
    if (strcmp(account->password, old_password) != 0)
    {
        KPrint("account %s password error!\n", account->name);
        return -1;
    }
    if (!strcmp(account->password, new_password))
    {
        KPrint("account new password no change\n");
        return 0;
    }
    // clear password
    memset(account->password, 0, sizeof(ACCOUNT_PASSWORD_LEN));
    // reset password
    memcpy(account->password, new_password, min(ACCOUNT_PASSWORD_LEN, strlen(new_password)));
    account->password[strlen(new_password)] = 0;
    SpinUnlock(&account->lock);
    // login verity
    if (AccountLogin(name, new_password) < 0)
    {
        KPrint("account change password error, a unknow error occur!\n");
        return -1;
    }
    return 0;
}

int SysLogin(const char *name, const char *password)
{
    if (!name || !password)
        return -EINVAL;
    if (SafetyCheckRange((void *)name, ACCOUNT_NAME_LEN))
        return -EINVAL;
    if (SafetyCheckRange((void *)password, ACCOUNT_PASSWORD_LEN))
        return -EINVAL;
    return AccountLogin(name, password);
}

int SysLogout(const char *name)
{
    if (!name)
        return -EINVAL;
    if (SafetyCheckRange((void *)name, ACCOUNT_NAME_LEN))
        return -EINVAL;
    return AccountLogout(name);
}

int SysAccountVerify(const char *password)
{
    if (!cur_user)
        return -EFAULT;
    if (SafetyCheckRange((void *)password, ACCOUNT_PASSWORD_LEN) < 0)
        return -EFAULT;
    if (AccountPasswordCheck((void *)password) < 0)
        return -EINVAL;
    SpinLock(&cur_user->lock);
    if (strcmp(password, cur_user->password) != 0)
    {
        KPrint("[acount] verity account %s err\n", cur_user->name);
        MutexlockUnlock(&account_mutex_lock);
        return -1;
    }
    KPrint("[account] verify ok\n");
    SpinUnlock(&cur_user->lock);
    return 0;
}

int SysRegister(const char *name, char *password)
{
    if (!name)
        return -EINVAL;
    if (SafetyCheckRange((void *)name, ACCOUNT_NAME_LEN))
        return -EINVAL;
    if (!password)
        return -EINVAL;
    if (SafetyCheckRange((void *)password, ACCOUNT_PASSWORD_LEN))
        return -EINVAL;
    return AccountRegister((void *)name, (void *)password, ACCOUNT_LEVEL_USER);
}

int SysUnRegister(const char *name)
{
    if (!name)
        return -EINVAL;
    if (SafetyCheckRange((void *)name, ACCOUNT_NAME_LEN))
        return -EINVAL;

    return AccountUnRegister(name);
}

int SysAccountName(const char *buff, size_t len)
{
    if (!buff)
        return -EINVAL;
    if (!cur_user)
        return -EFAULT;
    return MemCopyToUser((void *)buff, cur_user->name, MIN(strlen(cur_user->name), len));
}

int SysAccountChPwd(const char *name, const char *old_password, const char *new_password)
{
    if (!name || !old_password || !new_password)
        return -EINVAL;

    return AccountChPwd((void *)name, (void *)old_password, (void *)new_password);
}

int SysGetUID()
{
    return cur_user->uid;
}

int SysGetGID()
{
    return cur_user->gid;
}